package com.redistool;

import com.dronehome.base.IRedisCache;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Service;

import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * Redis缓存实现
 */
@Slf4j
@Service
public class RedisUtils implements IRedisCache {

    private StringRedisTemplate stringRedisTemplate;

    private static final Long DEFAULT_TTL = 5 * 60 * 1000L;

    public RedisUtils(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 获取缓存对象
     * @param key 键值
     * @param classOfT 对象类型
     * @param supplier 获取缓存失败时，调用的方法
     * @return 内容
     */
    @SneakyThrows
    @Override
    public <T> T get(String key, Class<T> classOfT, Supplier<T> supplier) {
        return get(key, classOfT, DEFAULT_TTL, supplier);
    }
    /**
     * 获取缓存对象
     * @param key 键值
     * @param classOfT 对象类型
     * @param supplier 获取缓存失败时，调用的方法
     * @param timeout 超时时间
     * @return 内容
     */
    @SneakyThrows
    public <T> T get(String key, Class<T> classOfT, Long timeout, Supplier<T> supplier) {
        T value = get(key, classOfT);
        if (value == null) {
            value = supplier.get();
            set(key, value, timeout);
        }
        return value;
    }

    /**
     * 获取缓存对象 list
     * @param key 键值
     * @param valueTypeRef
     * @param timeout 超时时间
     * @param supplier 获取缓存失败时，调用的方法
     * @return
     * @param <T>
     */
    @SneakyThrows
    public <T> T getList(String key, TypeReference<T> valueTypeRef, Long timeout, Supplier<T> supplier) {
        T value = get(key, valueTypeRef);
        if (value == null) {
            value = supplier.get();
            set(key, value, timeout);
        }
        return value;
    }

    /**
     * 获取缓存对象
     * @param key 键值
     * @param classOfT 对象
     * @return
     * @param <T>
     */
    @Override
    public <T> T get(String key, Class<T> classOfT) {
        String strValue = stringRedisTemplate.opsForValue().get(key);
        if (strValue != null) {
            if (classOfT == String.class) {
               return (T) strValue;
            } else {
                return JsonUtils.convertJsonToObject(strValue, classOfT);
            }
        } else {
            return null;
        }
    }

    @SneakyThrows
    public   <T> T get(String key, TypeReference<T> valueTypeRef) {
        String strValue = stringRedisTemplate.opsForValue().get(key);
        if (strValue != null) {
            return JsonUtils.convertJsonToObject(strValue, valueTypeRef);
        } else {
            return null;
        }
    }

    /**
     * 获取缓存字符串
     * @param key 键值
     * @param supplier 获取缓存失败时，调用的方法
     * @return
     */
    @SneakyThrows
    @Override
    public String get(String key, Supplier<String> supplier) {
        String value = get(key);
        if (value == null) {
            value = supplier.get();
            set(key, value);
        }
        return value;
    }

    /**
     *  获取缓存字符串
     * @param key 键值
     * @return
     */
    @Override
    public String get(String key) {
        return stringRedisTemplate.opsForValue().get(key);
    }
    /**
     * 设置缓存对象
     * @param key 键值
     * @param value 值
     */
    @Override
    public void set(String key, Object value) {
        set(key, value, DEFAULT_TTL);
    }

    /**
     * 设置缓存对象
     * @param key 键值
     * @param value 值
     * @param timeout 超时时间
     */
    public void set(String key, Object value, Long timeout) {
        set(key, value, timeout, true);
    }
    /**
     * 设置缓存对象
     * @param key 键值
     * @param value 值
     * @param isExpired 是否过期
     */
    public void set(String key, Object value, boolean isExpired) {
        set(key, value, DEFAULT_TTL, isExpired);
    }

    /**
     * 设置缓存对象
     * @param key 键值
     * @param value 值
     * @param timeout 超时时间
     * @param isExpired 是否过期
     */
    public void set(String key, Object value, Long timeout, boolean isExpired) {
        if (value != null) {
            String stringValue;
            if (value instanceof String) {
                stringValue = value.toString();
            } else {
                stringValue = JsonUtils.convertObjectToJson(value);
            }

            if (isExpired) {
                stringRedisTemplate.opsForValue().set(key, stringValue, timeout, TimeUnit.MILLISECONDS);
            } else {
                stringRedisTemplate.opsForValue().set(key, stringValue);
            }
        }
    }

    /**
     * 获取缓存键
     * @param prefix 前缀
     * @return
     */
    public Set<String> keys(String prefix) {
        return stringRedisTemplate.keys(prefix + "*");
    }

    /**
     * 删除缓存键
     * @param key 键值
     */
    @Override
    public void delete(String key) {
        stringRedisTemplate.delete(key);
    }

    /**
     * 模糊删除缓存键
     * @param prefix 前缀
     */
    public void deleteKeys(String prefix) {
        Set<String> keys = keys(prefix);
        if (CollectionUtils.isNotEmpty(keys)) {
            for (String key : keys) {
                stringRedisTemplate.delete(key);
            }
        }
    }
    /**
     * 判断缓存键是否存在
     * @param key 键值
     * @return
     */
    public boolean exists(String key) {
        return stringRedisTemplate.hasKey(key);
    }
    /**
     * 增加缓存值
     * @param key 键值
     * @param value 值
     * @return
     */
    public Long increment(String key, long value) {
        return stringRedisTemplate.opsForValue().increment(key, value);
    }
    /**
     * 增加缓存值
     * @param key 键值
     * @param value 值
     * @param timeout 超时时间
     * @return
     */
    public Long increment(String key, long value, Long timeout) {
        Long incrementValue = increment(key, value);
        expire(key, timeout);
        return incrementValue;
    }
    /**
     * 减少缓存值
     * @param key 键值
     * @param value 值
     * @return
     */
    public Long decrement(String key, long value) {
        return stringRedisTemplate.opsForValue().decrement(key, value);
    }
    /**
     * 减少缓存值
     * @param key 键值
     * @param value 值
     * @param timeout 超时时间
     * @return
     */
    public Long decrement(String key, long value, Long timeout) {
        Long decrementValue = decrement(key, value);
        expire(key, timeout);
        return decrementValue;
    }
    /**
     * 设置过期时间
     * @param key 键值
     * @param timeout 超时时间
     */
    public void expire(String key, Long timeout) {
        stringRedisTemplate.expire(key, timeout, TimeUnit.MILLISECONDS);
    }

    /**
     * 查询key键值的过期时间
     *
     * @param key 键值
     * @return 返回为-1时，此键值没有设置过期时间；
     * 返回为-2时，不存在此键
     */
    public Long getExpire(String key) {
        return stringRedisTemplate.getExpire(key, TimeUnit.MILLISECONDS);
    }
    /**
     * 获取分布式锁
     * @param key 键值
     * @param timeout 超时时间
     * @return
     */
    public boolean setNX(String key, Long timeout) {
        return Boolean.TRUE.equals(stringRedisTemplate.execute((RedisCallback<Boolean>) connection -> {
            RedisSerializer<String> springSerializer = stringRedisTemplate.getStringSerializer();
            byte[] keyByte = springSerializer.serialize(key);
            byte[] valueByte = springSerializer.serialize(System.currentTimeMillis() + timeout + "");
            boolean success = connection.setNX(keyByte, valueByte);
            if (success) {
                connection.pExpire(keyByte, timeout);
            } else {
                try {
                    Long time = stringRedisTemplate.getExpire(key);
                    if (time <= -1) {
                        String valueStr = springSerializer.deserialize(connection.get(keyByte));
                        if (valueStr != null) {
                            Long value = Long.valueOf(valueStr);
                            if (System.currentTimeMillis() > (value + 1000)) {
                                connection.del(keyByte);
                            }
                        }
                    }
                } catch (Exception e) {
                    log.error("redisUtils delete NxKey failure :", e);
                }
            }
            return success;
        }));
    }


}
